/* servobj cli for service object */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <linux/netfilter.h>
#include "addrobj.h"

static struct option opts[] =
{
	{"add",1,0,'A'},
	{"delete",1,0,'D'},
	{"modify",1,0,'M'},
	{"exist",1,0,'E'},
	{"show",1,0,'S'},
	{"flush",1,0,'F'},
	{"family",1,0,'f'},
	{"number",1,0,'n'},
	{"inv",1,0,'i'},
	{NULL},
};

#define OPERATE_FOUND	1 
#define OPERATE_ADD	2 
#define OPERATE_DELETE	4

#define ADD	1
#define DELETE	2
#define ADD_TO	3
#define DELETE_FROM 4
#define EXIST	5
#define SHOW	6
#define FLUSH	7

static char     *PROG;

typedef struct network_addr {
        in_addr_t addr;
        int pfx;
} network_addr_t;

/* Compute netmask address given prefix */
in_addr_t netmask( int prefix ) 
{
        if ( prefix == 0 )
                return( ~((in_addr_t) -1) );
        else
                return( ~((1 << (32 - prefix)) - 1) );
} 

/* Compute network address given address and prefix */
in_addr_t network( in_addr_t addr, int prefix ) 
{
        return( addr & netmask(prefix) );
}

in_addr_t a_to_hl( char *ipstr )
{
        struct in_addr in;

        if ( !inet_aton(ipstr, &in) ) {
                fprintf( stderr, "%s: Invalid address %s!\n", PROG, ipstr );
                exit( 1 );
        }

        return( ntohl(in.s_addr) );
} 

network_addr_t str_to_netaddr( char *ipstr ) 
{
        long int prefix = 32;
        char *prefixstr;
        network_addr_t netaddr;

        if ( (prefixstr = strchr(ipstr, '/')) ) {
                *prefixstr = '\0';
                prefixstr++;
                prefix = strtol( prefixstr, NULL, 10 );
                if ( errno || (*prefixstr == '\0') || (prefix < 0) || (prefix > 32) ) {
                        fprintf( stderr, "%s: Invalid prefix /%s...!\n", PROG, prefixstr );
                        exit( 1 );
                }
        }

        netaddr.pfx = (int) prefix;
        netaddr.addr = network( a_to_hl(ipstr), prefix );
        return netaddr;

} /* str_to_netaddr() */

static int
str_to_range( char *ipstr, struct in_addr *min, struct in_addr *max )
{
	network_addr_t ip;
	u32 mask;

	ip = str_to_netaddr( ipstr ) ;
	
	mask = ~(0xFFFFFFFF >> ip.pfx);
	min->s_addr = htonl(ip.addr & mask);
	max->s_addr = htonl((ip.addr & mask) | ~mask);
	return 0;
}

static inline void
in4cpy(struct in_addr *dest, const struct in_addr *src)
{
	dest->s_addr = src->s_addr;
}

static inline void
in6cpy(struct in6_addr *dest, const struct in6_addr *src)
{
	memcpy(dest, src, sizeof(struct in6_addr));
}

static void
copy_addr(uint8_t family, union nf_inet_addr *ip, const void *value)
{
	if (family == NFPROTO_IPV4)
		in4cpy(&ip->in, value);
	else
		in6cpy(&ip->in6, value);
}

/**************** Sockopt SET cmd *****************/
static int
addrobj_add_obj( const char *name, u8 family )
{
	int ret = 0, size = 0;
	int sockfd = -1;
	addr_range_request_t *req = NULL;

	/* Alloc addrobj request struct and initiatize */
	size = sizeof( addr_range_request_t );
	req = (addr_range_request_t *)malloc( size );
	if ( NULL == req ) {
		printf( "Alloc memory for addrobj_delete_obj failed!\n" );
		return -ENOMEM;
	}

	memset( req, 0, sizeof(addr_range_request_t) );
	strncpy( req->name, name, MAX_OBJ_NAME_LEN );
	req->family = family;
		
	/* Create socket and socket set */
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 ) {
		printf( "socket system call failed!\n" );
		ret = -errno;
		goto free_req_mm;
	}

	ret = setsockopt( sockfd, IPPROTO_IP, ADDR_RANGE_OBJ_ADD, req, size );
	if ( ret != 0 ) {
		printf( "can't delete service group object(%s)!\n", req->name );
		ret = -errno;
	}

free_sock:
	close( sockfd );
free_req_mm:
	free( req );
	return ret;
}


int
addrobj_delete_obj( const char *name, u8 family )
{
	int ret = 0, size = 0;
	int sockfd = -1;
	addr_range_request_t *req = NULL;

	/* Alloc addrobj request struct and initiatize */
	size = sizeof( addr_range_request_t );
	req = (addr_range_request_t *)malloc( size );
	if ( NULL == req ) {
		printf( "Alloc memory for addrobj_delete_obj failed!\n" );
		return -ENOMEM;
	}
	memset( req, 0, sizeof(addr_range_request_t) );
	strncpy( req->name, name, MAX_OBJ_NAME_LEN );
	req->family = family;
		
	/* Create socket and socket set */
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 ) {
		printf( "socket system call failed!\n" );
		ret = -errno;
		goto free_req_mm;
	}
		
	ret = setsockopt( sockfd, IPPROTO_IP, ADDR_RANGE_OBJ_DELETE, req, size );
	if ( ret != 0 ) {
		printf( "can't delete service group object(%s)!\n", req->name );
		ret = -errno;
	}

free_sock:
	close( sockfd );
free_req_mm:
	free( req );
	return ret;
}

static int 
addrobj_modify_info_to_obj( const char *name ,u8 family, struct in_addr min, struct in_addr max, u8 flags, int cmd )
{
	addr_range_request_t *req = NULL;
	int sockfd = -1, size = 0, ret = 0;

	/* Alloc addrobj request struct and initiatize */
	size = sizeof( addr_range_request_t );
	req = (addr_range_request_t *)malloc( size );
	if ( NULL == req ) {
		printf( "Alloc memory for addrobj_delete_obj failed!\n" );
		return -ENOMEM;
	}
	memset( req, 0, sizeof(addr_range_request_t) );
	strncpy( req->name, name, MAX_OBJ_NAME_LEN );
	req->family = family;
	req->flags = flags;
	copy_addr( family, &req->range.startip, (void*)&min );
	copy_addr( family, &req->range.endip, (void*)&max );	
		
	/* Create socket and socket set */
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 ) {
		printf( "socket system call failed!\n" );
		ret = -errno;
		goto free_req_mm;
	}
		
	ret = setsockopt( sockfd, IPPROTO_IP, cmd, req, size );
	if ( ret != 0 ) {
		printf( "can't operation addr object(%s)!\n", req->name );
		ret = -errno;
	}

free_sock:
	close( sockfd );
free_req_mm:
	free( req );
	return ret;
}

static int
addrobj_add_info_to_obj( const char *name, u8 family, char *ipstr, u8 flags )
{
	struct in_addr min, max;
	str_to_range( ipstr, &min, &max );
	return addrobj_modify_info_to_obj( name , family, min, max, flags, ADDR_RANGE_CELL_ADD );
}

static int
addrobj_del_info_to_obj( const char *name, u8 family, char *ipstr, u8 flags )
{
	struct in_addr min, max;
	str_to_range( ipstr, &min, &max );	
	return addrobj_modify_info_to_obj( name , family, min, max, flags, ADDR_RANGE_CELL_DELETE );
}

static int
addrobj_clean_obj( const char *name, u8 family )
{
	addr_range_request_t *req = NULL;
	int ret = 0, sockfd = -1;
	size_t size = 0; 

	size = sizeof( addr_range_request_t );
	req = (addr_range_request_t *)malloc( size );
	if (  NULL == req ) {
		printf( "Alloc memory for addrobj_delete_obj failed!\n" );
		return -ENOMEM;	
	}
	

	strncpy( req->name, name, MAX_OBJ_NAME_LEN );
	req->family = family;

	/* Create socket and socket set */
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 ) {
		ret = -errno;
		goto free_req_mm;
	}
	
	ret = setsockopt( sockfd, IPPROTO_IP, ADDR_RANGE_OBJ_FLUSH, req, size );
	if ( ret != 0 ) {
		printf( "can't clean family(%u) table!\n", req->family );
		ret = -errno;
	}
	
free_sock:
	close( sockfd );
free_req_mm:
	free( req );
	return ret;
}

/**************** Sockopt Get *****************/
static int
addrobj_exist_obj( const char *name, u8 family )
{
	int ret = 0, sockfd = -1;
	size_t len = 0;
	addr_range_request_t *req = NULL;

	/* Alloc addrobj request struct and initiatize */
	len = sizeof( addr_range_request_t );
	req = ( addr_range_request_t* )malloc( len );
	if ( NULL == req ) {
		printf( "Alloc memory for addrobj_delete_obj failed!\n" );
		ret = -ENOMEM;
		return ret;
	}
	memset( req, 0, sizeof(addr_range_request_t) );
	strncpy( req->name, name, MAX_OBJ_NAME_LEN );
	req->family = family;
			
	/* Create socket and socket set */
	sockfd = socket( PF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 ) {
		printf( "socket system call failed!\n" );
		return -errno;
	}

	ret=getsockopt( sockfd, IPPROTO_IP, ADDR_RANGE_OBJ_EXIST, req, &len );	
	if ( ret != 0 ) {
		printf( "can't delete service group object(%s)!\n", req->name );
		ret = -errno;
		goto free_sock;
	}

free_sock:
	close( sockfd );
	return ret == 0 ? len : ret;	
}

/* Socketopt operation function end */
int main( int argc, char **argv )
{
	int c = 0, ret = -1;
	int optflags = 0;
	char name[MAX_OBJ_NAME_LEN] = {0};
	char ipstr[34] = {0};
	u8 family = 0;
	u8 flags = 0;
	int operate = 0;
	
	while ( (c = getopt_long(argc, argv, "-A:D:S:E:M:F:f:n:i:", opts, NULL)) != -1 ) {
		switch ( c ) {
		case 'A':
			if ( optflags & OPERATE_FOUND ){
				printf( "can not define operation more than once!\n" );
				return -1;
			}
			operate = ADD;
			strncpy( name, optarg, MAX_OBJ_NAME_LEN );
			optflags |= OPERATE_FOUND;
			optflags |= OPERATE_ADD;
			break;

		case 'D':
			if ( optflags & OPERATE_FOUND ) {
				printf( "can not define operation more than once!\n" );
				return -1;
			}
			operate = DELETE;
			strncpy( name, optarg, MAX_OBJ_NAME_LEN );
			optflags |= OPERATE_FOUND;
			optflags |= OPERATE_DELETE;
			break;			

		case 'S':
			if ( optflags & OPERATE_FOUND ) {
				printf( "can not define operation more than once!\n" );
				return -1;
			}
			operate = SHOW;
			strncpy( name, optarg, MAX_OBJ_NAME_LEN );
			optflags |= OPERATE_FOUND;
			break;
		case 'E':
			if ( optflags & OPERATE_FOUND ) {
				printf( "can not define operation more than once!\n" );
				return -1;
			}
			operate = EXIST;
			strncpy( name, optarg, MAX_OBJ_NAME_LEN );
			optflags |= OPERATE_FOUND;
			break;	
		case 'M':
			if ( optflags & OPERATE_FOUND ) {
				printf( "can not define operation more than once!\n" );
				return -1;
			}
			//operate = MODIFY;
			strncpy( name, optarg, MAX_OBJ_NAME_LEN );
			optflags |= OPERATE_FOUND;
			break;	
		case 'F':
			if ( optflags & OPERATE_FOUND ) {
				printf( "can not define operation more than once!\n" );
				return -1;
			}
			operate = FLUSH;
			strncpy( name, optarg, MAX_OBJ_NAME_LEN );
			optflags |= OPERATE_FOUND;
			
			break;
		case 'f':
			family = atoi( optarg );
			break;
		case 'n':
			strncpy( ipstr, optarg, 33 );
			if ( optflags & OPERATE_ADD ) {
				operate = ADD_TO;
				break;
			}
			if ( optflags & OPERATE_DELETE ) {
				operate = DELETE_FROM;
				break;
			}
			ret = -1;
			return ret;
		case 'i':
			if ( strncmp(optarg, "inv", 3) == 0 ) {
				flags |= ADDROBJ_INV;
				break;
			}
			printf( "Error paramers!\n" );
			return -1;
		default:
			printf( "1 Unknow command for addrobj!\n" );
			return -1;
		}
	}

	if ( !(optflags & OPERATE_FOUND) ) {
		printf( "Operation failed!\n" );
		return -1;
	}
	switch ( operate ) {
	case ADD:
		ret = addrobj_exist_obj( name, family );
		if ( ret ) {
			return -EEXIST;
		} 
		
		ret = addrobj_add_obj( name, family );
		if ( 0 != ret ) {
			return -1;
		}
		break;
	case DELETE:
		ret = addrobj_exist_obj( name, family );
		if ( !ret ) {
			return -1;
		}
		ret = addrobj_delete_obj( name, family );
		if ( 0 != ret ) {
			return -1;
		}
		break;
	case ADD_TO:
		ret = addrobj_add_info_to_obj( name, family, ipstr, flags );
		break;
	case DELETE_FROM:
		ret = addrobj_del_info_to_obj( name, family, ipstr, flags );
		break;
	case EXIST:
		ret = addrobj_exist_obj( name, family );
		break;
	case FLUSH:
		ret = addrobj_clean_obj( name, family );
		break;
	default:
		printf( "2 Unknow command for addrobj!!\n" );
		ret = -1;
		break;
	}	

	return ret;
}
